﻿// (c)2010 David Schwarz (david.schwarz@live.com)
// This file is licensed under GNU GPL v2 license, see more at http://www.gnu.org/licenses/gpl-2.0.html
namespace Z2.Core
{

	#region Using

	using System;
	using System.Timers;
	using Microsoft.Xna.Framework;
	using Microsoft.Xna.Framework.Graphics;

	#endregion

	/// <summary>
	/// Základní třída pro všechny objekty ve hře
	/// </summary>
	public class ObjectBase : IDisposable
	{

		#region Const

		private const int MODIFY_VECTOR_TICK = 10;
		private const float FRICTION_DEFAULT = 0.5f;
		
		#endregion

		#region Members

		private WorldSector m_sector; // Sektor v herní mapě, kde se tento objekt nachází

		private ObjectStateHorizontal m_horizontalState; // Horizontální stav objektu
		private ObjectStateVertical m_verticalState; // Vertikální stav objektu

		protected Vector2 m_initialPosition; // Výchozí pozice objektu (nemění se)
		protected Vector2 m_position; // Pozice objektu
		protected Vector2 m_move; // Vektor, který mění m_position
		private Vector2 m_change; // Zde se ukládá hodnta pro změnu vektoru m_move v obou osách

		private Sprite m_sprite; // Sprite asociovaný s objektem

		private Timer m_modifyVector; // Timer pro modifikaci vektoru
		private Timer m_normalizeVector; // Timer pro normalizaci vektoru

		private float m_friction; // Tření objektu
		private float m_maxSpeedHorizontal; // Max. rychlost v ose X

		private bool m_disposed;

		private ObjectType m_type; // Typ objektu
		private Rectangle m_collisionRect; // Kolizní rectangle
		private ObjectBehaviorBase m_behavior; // Definuje chování objektu

		#endregion

		#region Constructor

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="sprite"><see cref="Sprite"/></param>
		public ObjectBase(Sprite sprite)
		{
			this.m_position = new Vector2();
			this.m_sprite = sprite;

			this.m_modifyVector= new Timer();
			this.m_modifyVector.Interval = ObjectBase.MODIFY_VECTOR_TICK;
			this.m_modifyVector.Elapsed += new ElapsedEventHandler(this.ModifyVector);
			this.m_modifyVector.Enabled = true;

			this.m_normalizeVector = new Timer();
			this.m_normalizeVector.Interval = 10;
			this.m_normalizeVector.Elapsed += new ElapsedEventHandler(this.NormalizeVector);
			this.m_normalizeVector.Enabled = true;

			this.m_friction = ObjectBase.FRICTION_DEFAULT;

			this.m_maxSpeedHorizontal = -1;

			this.m_verticalState = ObjectStateVertical.Idle;
			this.m_horizontalState = ObjectStateHorizontal.Idle;

			this.m_collisionRect = new Rectangle(0, 0, this.m_sprite.FrameWidth, this.m_sprite.FrameHeight);
		}

		#endregion

		#region Properties

		internal WorldSector Sector
		{
			get
			{
				return this.m_sector;
			}
			set
			{
				this.m_sector = value;
			}
		}

		/// <summary>
		/// Vrací vektor, který mění pohybový vektor <see cref="Move"/>
		/// </summary>
		internal Vector2 Change
		{
			get
			{
				return this.m_change;
			}
		}

		/// <summary>
		/// Nastaví nebo vrátí "chování" objektu
		/// </summary>
		internal ObjectBehaviorBase Behavior
		{
			get
			{
				return this.m_behavior;
			}
			set
			{
				this.m_behavior = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje kolizní rectangle
		/// </summary>
		protected Rectangle CollisionRect
		{
			get
			{
				return this.m_collisionRect;
			}
			set
			{
				this.m_collisionRect = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje typ objektu
		/// </summary>
		public ObjectType Type
		{
			get
			{
				return this.m_type;
			}
			set
			{
				this.m_type = value;
			}
		}

		/// <summary>
		/// Vrací pohybový vektor, který mení pozici objektu.
		/// </summary>
		internal Vector2 Move
		{
			get
			{
				return this.m_move;
			}
			set
			{
				this.m_move = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje vertikální stav objektu
		/// </summary>
		public ObjectStateVertical VerticalState
		{
			get
			{
				return this.m_verticalState;
			}
			internal set
			{
				this.m_verticalState = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje horizontální stav objektu
		/// </summary>
		public ObjectStateHorizontal HorizontalState
		{
			get
			{
				return this.m_horizontalState;
			}
			internal set
			{
				this.m_horizontalState = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje tření
		/// </summary>
		public float Friction
		{
			get 
			{
				return this.m_friction;
			}
			set
			{
				this.m_friction = Math.Abs(value);
			}
		}

		/// <summary>
		/// Vrací sprite objektu
		/// </summary>
		public Sprite Sprite
		{
			get
			{
				return this.m_sprite;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje původní pozici objektu
		/// </summary>
		public Vector2 InitialPosition
		{
			get
			{
				return this.m_initialPosition;
			}
			set
			{
				this.m_initialPosition = value;
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje pozici objektu
		/// </summary>
		public Vector2 Position
		{
			get
			{
				return this.m_position;
			}
			set
			{
				this.m_position = value;
				this.OnPositionChanged();
			}
		}

		/// <summary>
		/// Vrací nebo nastavuje maximální rychlost v ose X
		/// </summary>
		public float MaxHorizontalSpeed
		{
			get
			{
				return this.m_maxSpeedHorizontal;
			}
			set
			{
				this.m_maxSpeedHorizontal = value;
			}
		}

		#endregion

		#region Methods

		protected void OnPositionChanged()
		{
			World.Current.RegisterToSector(this);
		}

		/// <summary>
		/// Metoad mění vektor <see cref="Move"/> v ose Y
		/// </summary>
		/// <param name="value">Hodnota</param>
		public void ChangeVertical(float value)
		{
			if (value != 0 && this.VerticalState == ObjectStateVertical.Idle)
			{
				this.m_verticalState = value > 0 ? ObjectStateVertical.Falling : ObjectStateVertical.Rising;
				this.OnStartMovingVertical();
			}

			this.m_move.Y = value;
		}

		/// <summary>
		/// Metoad mění vektor <see cref="Move"/> v ose X
		/// </summary>
		/// <param name="value">Hodnota</param>
		public void ChangeHorizontal(float value)
		{
			if (value != 0 && this.HorizontalState == ObjectStateHorizontal.Idle)
			{
				this.m_horizontalState = value > 0 ? ObjectStateHorizontal.MovingRight : ObjectStateHorizontal.MovingLeft;
				this.OnStartMovingHorizontal();
			}

			this.m_move.X = value;
		}

		/// <summary>
		/// Pohyb objektem v horizontálním směru
		/// </summary>
		/// <param name="value">Hodnota pohybu</param>
		public void MoveHorizontal(float value)
		{
			// Pokud je X hodnota vektoru nulová (tzn., že objekt se nehýbe v ose X)
			if (value != 0 && this.HorizontalState == ObjectStateHorizontal.Idle)
			{
				this.m_horizontalState = (this.m_move.X + value) > 0 ? 
					ObjectStateHorizontal.MovingRight : ObjectStateHorizontal.MovingLeft;
				// Začíná se pohybovat v ose X
				this.OnStartMovingHorizontal();
			}

			this.m_change.X = value; // Aktualizace vektoru
		}

		/// <summary>
		/// Pohyb objektem ve vertikálním směru
		/// </summary>
		/// <param name="value">Hodnota pohybu</param>
		public void MoveVertical(float value)
		{
			if (value != 0 && this.VerticalState == ObjectStateVertical.Idle)
			{
				this.m_verticalState = (this.m_move.Y + value) > 0 ? 
					ObjectStateVertical.Falling : ObjectStateVertical.Rising;
				// Pohybuje se v ose Y
				this.OnStartMovingVertical();
			}
			// Aktualizavce vektoru
			this.m_change.Y = value;
		}

		/// <summary>
		/// Vykreslení objektu
		/// </summary>
		/// <param name="batch"><see cref="SpriteBatch"/></param>
		public void Draw(SpriteBatch batch)
		{
			this.m_sprite.Draw(batch, Vector2.Add(this.m_position, Camera.Instance.Position));
		}

		/// <summary>
		/// Metoda otestuje tzv. falešnou kolizi v ose X. Používá se k pohybu objektu 
		/// směrem vzhůru, pokud je to možné. (=chození do kopce atp.).
		/// </summary>
		/// <param name="result">Výsledek předchozího testu kolize</param>
		/// <returns><strong>True</strong>, pokud se jedná o falešnou kolizi a objekt se může pohybovat směrem vzhůru.</returns>
		internal bool CheckFalseCollision(IntersectionResult result)
		{
			float val = (this.m_position.Y + this.m_sprite.Height) - result.Rectangle.Top;
			if ((result.With == null || result.With.Type == ObjectType.Bendable) &&
				val <= WorldField.SIZE && this.VerticalState == ObjectStateVertical.Idle)
			{
				this.m_position.Y = result.Rectangle.Top - this.m_sprite.Height - 2;
				return true;
			}
			return false;
		}

		/// <summary>
		/// Metoda otesuje kolizi tohoto objektu s objektem <paramref name="objectBase"/>
		/// </summary>
		/// <param name="objectBase">Objekt, se kterým se testuje kolize.</param>
		/// <param name="rect">Kolizní rect tohoto objektu (modifikovaný)</param>
		/// <returns>Výsledek kolize</returns>
		public IntersectionResult Intersects(ObjectBase objectBase, Rectangle rect)
		{
			Rectangle objectBaseRectangle = new Rectangle(
				(int)objectBase.m_position.X,
				(int)objectBase.m_position.Y,
				objectBase.m_sprite.FrameWidth,
				objectBase.m_sprite.FrameHeight);

			if (rect.Intersects(objectBaseRectangle))
			{
				IntersectionResult result = new IntersectionResult();
				result.Rectangle = objectBaseRectangle;
				result.HorizontalType = (rect.Center.X > objectBaseRectangle.Center.X) ? IntersectionHorizontalType.FromRight : IntersectionHorizontalType.FromLeft;
				result.VerticalType = (rect.Center.Y > objectBaseRectangle.Center.Y) ? IntersectionVerticalType.FromBottom : IntersectionVerticalType.FromTop;
				result.IsCollision = true;
				result.With = objectBase;

				return result;
			}

			return IntersectionResult.None;
		}

		/// <summary>
		/// Metoda se volá při kolizi v ose X
		/// </summary>
		/// <param name="result">Výsledek testu kolize</param>
		internal void OnHorizontalCollision(IntersectionResult result)
		{
			if (result.With == null)
			{
				this.m_move.X = 0; // Objekt se nemůže tímto směrem hýbat
			}

			if (result.HorizontalType == IntersectionHorizontalType.FromLeft)
			{
				if (result.With == null)
				{
					this.m_position.X = result.Rectangle.Left - (float)this.Sprite.FrameWidth;
				}
				else
				{
					this.m_position.X = result.With.Position.X - (float)this.Sprite.FrameWidth;
				}
			}
			else
			{
				if (result.With == null)
				{
					this.m_position.X = result.Rectangle.Right;
				}
				else
				{
					this.m_position.X = result.With.Position.X + (float)result.With.Sprite.FrameHeight;
				}
			}

			this.OnPositionChanged();
		}

		/// <summary>
		/// Metoda se volá pri kolizi v ose Y
		/// </summary>
		/// <param name="result">Výsledek kolize</param>
		internal void OnVerticalCollision(IntersectionResult result)
		{
			this.m_move.Y = 0;

			// Pokud byla kolize ze spoda
			if (result.VerticalType == IntersectionVerticalType.FromBottom)
			{
				if (result.With == null)
				{
					this.m_position.Y = result.Rectangle.Bottom; // Korekce pozice
				}
				else
				{
					this.m_position.Y = result.With.Position.Y + (float)result.With.Sprite.FrameHeight;
				}
			}
			else
			{
				if (result.With == null)
				{
					this.m_position.Y = result.Rectangle.Top - (float)this.Sprite.FrameHeight; // Korekce pozice
				}
				else
				{
					this.m_position.Y = result.With.Position.Y - (float)this.Sprite.FrameHeight;
				}

				this.m_verticalState = ObjectStateVertical.Idle;
				this.OnStopMovingVertical();
			}

			this.OnPositionChanged();
		}

		/// <summary>
		/// Metoda vrací modifikovaný kolizní rect tohoto objektu
		/// </summary>
		/// <param name="addHorizontal">Kolik přidat v ose X</param>
		/// <param name="addVertical">Kolik přidat v ose Y</param>
		/// <returns><see cref="Rectangle"/></returns>
		internal Rectangle GetRectangle(float addHorizontal, float addVertical)
		{
			this.m_collisionRect.X = (int)(this.m_position.X + addHorizontal);
			this.m_collisionRect.Y = (int)(this.m_position.Y + addVertical);

			return this.m_collisionRect;
		}

		#endregion 

		#region Virtual Methods

		/// <summary>
		/// Metoda se volá když se objekt začne pohybovat v ose Y
		/// </summary>
		internal virtual void OnStartMovingVertical()
		{
		}

		/// <summary>
		/// Metoda se volá, když se objekt přestane phybovat v ose Y
		/// </summary>
		internal virtual void OnStopMovingVertical()
		{
		}

		/// <summary>
		/// Metoda se volá, když se objekt přestane phybovat v ose X
		/// </summary>
		internal virtual void OnStopMovingHorizontal()
		{
		}

		/// <summary>
		/// Metoda se volá když se objekt začne pohybovat v ose X
		/// </summary>
		internal virtual void OnStartMovingHorizontal()
		{
		}

		#endregion

		#region Event Handling

		/// <summary>
		/// Metoda normalizuje vektor <see cref="Move"/> pouze v ose X
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void NormalizeVector(object sender, ElapsedEventArgs e)
		{
			if (this.m_move.X > 0)
			{
				this.m_move.X -= ObjectBase.FRICTION_DEFAULT;
			}
			if (this.m_move.X < 0)
			{
				this.m_move.X += ObjectBase.FRICTION_DEFAULT;
			}

			if (this.HorizontalState != ObjectStateHorizontal.Idle &&
				Math.Round(this.m_move.X, 0, MidpointRounding.ToEven) == 0)
			{
				this.m_horizontalState = ObjectStateHorizontal.Idle;
				this.m_move.X = 0;
				this.m_change.X = 0;
				this.OnStopMovingHorizontal();
			}
		}

		/// <summary>
		/// Metoda modifikuje vektor tohoto objektu
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected virtual void ModifyVector(object sender, ElapsedEventArgs e)
		{
			// Pokud není načten žádný svět, není co testovat
			if(World.IsLoading)
			{
				return;
			}

			// Každý objekt musí mít přiřazen nějaký typ chování
			if (this.m_behavior == null)
			{
				throw new NotSupportedException("Tento objekt nemá zadané chování.");
			}

			this.m_behavior.ModifyVector(this);
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (this.m_disposed)
			{
				return;
			}

			this.m_normalizeVector.Enabled = false;
			this.m_modifyVector.Enabled = false;
			this.m_modifyVector.Elapsed -= new ElapsedEventHandler(this.ModifyVector);
			this.m_normalizeVector.Elapsed -= new ElapsedEventHandler(this.NormalizeVector);

			this.m_disposed = true;
			GC.SuppressFinalize(this);
		}

		#endregion

	}
}
